package com.app.core.signature;

import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

@NoArgsConstructor
@Slf4j
public class SignatureDecoder extends AbstractSignature {
    private String sign;

    public SignatureDecoder(String path, String httpMethod, Map<String, String> headers, byte[] inputStreamBytes, Map<String, String> keySecretMapping, long verificationTime) throws NumberFormatException, InvalidKeyException {
        this(path, httpMethod, headers, null, inputStreamBytes, keySecretMapping, verificationTime);
    }

    public SignatureDecoder(String path, String httpMethod, Map<String, String> headers, String signatureMethod, byte[] inputStreamBytes, Map<String, String> keySecretMapping, long verificationTime) throws InvalidKeyException {
        super(path, httpMethod, headers, signatureMethod);

        try {
            long timestamp = Long.parseLong(this.getTimestamp());
            long currentTime = System.currentTimeMillis();
            if (timestamp < currentTime - verificationTime || timestamp > currentTime + verificationTime) {
                throw new InvalidKeyException(String.format("Out of time, client:[%s], server:[%s], verification:[%s]", timestamp, currentTime, verificationTime));
            }
        } catch (NumberFormatException e) {
            throw new NumberFormatException(String.format("Invalid timestamp, client:[%s]", this.getTimestamp()));
        }

        this.sign = this.getSign();
        this.secret = keySecretMapping.getOrDefault(this.getKey(), "");
        this.contentMD5Match(inputStreamBytes);
    }

    /** 比较header中的ca-content-md5和后端根据@RequestBody计算的md5，不同抛异常 */
    public void contentMD5Match(byte[] inputStreamBytes) throws InvalidKeyException {
        String serviceContentMD5 = this.getContentMD5();
        if (serviceContentMD5 != null && serviceContentMD5.length() > 0) {
            String contentMD5 = this.base64AndMD5(null == inputStreamBytes ? new byte[0] : inputStreamBytes);
            if (!contentMD5.equals(serviceContentMD5)) {
                throw new InvalidKeyException("Invalid Signature, Inconsistent MD5 Content !");
            }
        }

    }

    @Override
    public void build(Map<String, Object> paramsMap, Map<String, Object> formParamsMap) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        String signString = this.buildStringToSign(paramsMap, formParamsMap);
        log.info("签名明文: \n{}", signString);
        String serviceSign = this.stringToSign(signString, this.signatureMethod, this.secret);
        if (!serviceSign.equals(this.sign)) {
            throw new InvalidKeyException("Invalid Signature, The content of the signature is inconsistent !");
        }
    }

    /** 根据header中ca-signature-headers参与计算签名的header，拼接key:value参与加签 */
    @Override
    protected String buildHeaders() {
        Map<String, String> headersToSign = new TreeMap();
        String signatureHeaders = this.headers.get(AbstractSignature.CA_SIGNATURE_HEADERS);
        if (null == signatureHeaders) {
            return "";
        } else {
            String[] signatureHeader = signatureHeaders.split("\\,");
            String[] signatureHeaderArr = signatureHeader;
            int headerLength = signatureHeader.length;

            for (int i = 0; i < headerLength; ++i) {
                String key = signatureHeaderArr[i];
                String value = this.headers.get(key);
                headersToSign.put(key, value);
            }

            StringBuilder sb = new StringBuilder();
            Iterator it = headersToSign.entrySet().iterator();

            while (it.hasNext()) {
                Map.Entry<String, String> e = (Map.Entry) it.next();
                sb.append(e.getKey()).append(':').append(e.getValue()).append(LF);
            }

            return sb.toString();
        }
    }
}
